﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace CSharpExperiments
{
	// TODO: MatcherThen<T> and MatcherThenTempType<T, U> can probably be combined into
	// MatcherThen<T, T> and MatcherThen<T, U>.
	public static class Match
	{
		public static Matcher0<T> On<T>(T key)
		{
			return new Matcher0Impl<T>(key);
		}

		private struct Matcher0Impl<T> : Matcher0<T>
		{
			private T key;

			public Matcher0Impl(T key)
			{
				this.key = key;
			}

			public InclusiveMatcher0<T> Inclusive()
			{
				return new InclusiveMatcher0Impl<T>(key);
			}

			public MatcherCase<T, R> Return<R>()
			{
				return new MatcherCaseImpl<T, R>(key, false, default(R));
			}

			public MatcherThen<T> Case(Func<T, bool> predicate)
			{
				return new MatcherThenImpl<T>(key, predicate(key), false);
			}

			public MatcherThenTempType<T, U> Case<U>()
			{
				return new MatcherThenTempTypeImpl<T, U>(key, key is U, false);
			}
		}

		private struct InclusiveMatcher0Impl<T> : InclusiveMatcher0<T>
		{
			private T key;

			public InclusiveMatcher0Impl(T key)
			{
				this.key = key;
			}

			public InclusiveMatcherCase<T, R> Return<R>()
			{
				return new InclusiveMatcherCaseImpl<T,R>(key, new List<R>());
			}

			public InclusiveMatcherThen<T> Case(Func<T, bool> predicate)
			{
				return new InclusiveMatcherThenImpl<T>(key, predicate(key));
			}

			public InclusiveMatcherThenTempType<T, U> Case<U>()
			{
				return new InclusiveMatcherThenTempTypeImpl<T, U>(key, key is U);
			}
		}

		private struct MatcherCaseImpl<T> : MatcherCase<T>
		{
			private T key;
			private bool complete;

			public MatcherCaseImpl(T key, bool complete)
			{
				this.key = key;
				this.complete = complete;
			}
			
			public MatcherThen<T> Case(Func<T, bool> predicate)
			{
				return new MatcherThenImpl<T>(key, !complete && predicate(key), complete);
			}

			public MatcherThenTempType<T, U> Case<U>()
			{
				return new MatcherThenTempTypeImpl<T, U>(key, !complete && (key is U), complete);
			}

			public void Else(Action<T> func)
			{
				if (! complete)
					func(key);
			}
		}

		private struct MatcherThenImpl<T> : MatcherThen<T>
		{
			private T key;
			private bool doFunc;
			private bool complete;

			public MatcherThenImpl(T key, bool doFunc, bool complete)
			{
				this.key = key;
				this.doFunc = doFunc;
				this.complete = complete;
			}

			public MatcherCase<T> Then(Action<T> func)
			{
				if (doFunc)
					func(key);

				return new MatcherCaseImpl<T>(key, complete || doFunc);
			}
		}

		private struct MatcherThenTempTypeImpl<T, U> : MatcherThenTempType<T, U>
		{
			private T key;
			private bool doFunc;
			private bool complete;

			public MatcherThenTempTypeImpl(T key, bool doFunc, bool complete)
			{
				this.key = key;
				this.doFunc = doFunc;
				this.complete = complete;
			}

			public MatcherCase<T> Then(Action<U> func)
			{
				if (doFunc)
					func((U) (Object) key);

				return new MatcherCaseImpl<T>(key, complete || doFunc);
			}
		}

		private class MatcherCaseImpl<T, R> : MatcherCase<T, R>
		{
			private T key;
			private bool complete;
			private R result;

			public MatcherCaseImpl(T key, bool complete, R result)
			{
				this.key = key;
				this.complete = complete;
				this.result = result;
			}

			public override MatcherThen<T, R> Case(Func<T, bool> predicate)
			{
				return new MatcherThenImpl<T, R>(key, !complete && predicate(key), complete, result);
			}

			public override MatcherThenTempType<T, U, R> Case<U>()
			{
				return new MatcherThenTempTypeImpl<T, U, R>(key, !complete && (key is U), complete, result);
			}

			public override R Else(Func<T, R> func)
			{
				return complete ? result : func(key);
			}
		}

		private struct MatcherThenImpl<T, R> : MatcherThen<T, R>
		{
			private T key;
			private bool doFunc;
			private bool complete;
			private R result;

			public MatcherThenImpl(T key, bool doFunc, bool complete, R result)
			{
				this.key = key;
				this.doFunc = doFunc;
				this.complete = complete;
				this.result = result;
			}

			public MatcherCase<T, R> Then(Func<T, R> func)
			{
				return new MatcherCaseImpl<T, R>(key, doFunc || complete, doFunc ? func(key) : result);
			}
		}

		private struct MatcherThenTempTypeImpl<T, U, R> : MatcherThenTempType<T, U, R>
		{
			private T key;
			private bool doFunc;
			private bool complete;
			private R result;

			public MatcherThenTempTypeImpl(T key, bool doFunc, bool complete, R result)
			{
				this.key = key;
				this.doFunc = doFunc;
				this.complete = complete;
				this.result = result;
			}

			public MatcherCase<T, R> Then(Func<U, R> func)
			{
				return new MatcherCaseImpl<T, R>(key, doFunc || complete, doFunc ? func((U) (Object) key) : result);
			}
		}

		private struct InclusiveMatcherCaseImpl<T> : InclusiveMatcherCase<T>
		{
			private T key;

			public InclusiveMatcherCaseImpl(T key)
			{
				this.key = key;
			}

			public InclusiveMatcherThen<T> Case(Func<T, bool> predicate)
			{
				return new InclusiveMatcherThenImpl<T>(key, predicate(key));
			}

			public InclusiveMatcherThenTempType<T, U> Case<U>()
			{
				return new InclusiveMatcherThenTempTypeImpl<T, U>(key, key is U);
			}
		}

		private struct InclusiveMatcherThenImpl<T> : InclusiveMatcherThen<T>
		{
			private T key;
			private bool doFunc;

			public InclusiveMatcherThenImpl(T key, bool doFunc)
			{
				this.key = key;
				this.doFunc = doFunc;
			}

			public InclusiveMatcherCase<T> Then(Action<T> func)
			{
				if (doFunc)
					func(key);

				return new InclusiveMatcherCaseImpl<T>(key);
			}
		}

		private struct InclusiveMatcherThenTempTypeImpl<T, U> : InclusiveMatcherThenTempType<T, U>
		{
			private T key;
			private bool doFunc;

			public InclusiveMatcherThenTempTypeImpl(T key, bool doFunc)
			{
				this.key = key;
				this.doFunc = doFunc;
			}

			public InclusiveMatcherCase<T> Then(Action<U> func)
			{
				if (doFunc)
					func((U) (Object) key);

				return new InclusiveMatcherCaseImpl<T>(key);
			}
		}

		private class InclusiveMatcherCaseImpl<T, R> : InclusiveMatcherCase<T, R>
		{
			private T key;
			private List<R> results;

			public InclusiveMatcherCaseImpl(T key, List<R> results)
			{
				this.key = key;
				this.results = results;
			}
			
			public override InclusiveMatcherThen<T, R> Case(Func<T, bool> predicate)
			{
				return new InclusiveMatcherThenImpl<T, R>(key, predicate(key), results);
			}

			public override InclusiveMatcherThenTempType<T, U, R> Case<U>()
			{
				return new InclusiveMatcherThenTempTypeImpl<T, U, R>(key, key is U, results);
			}

			public override List<R> Eval()
			{
				return results;
			}
		}

		private struct InclusiveMatcherThenImpl<T, R> : InclusiveMatcherThen<T, R>
		{
			private T key;
			private bool doFunc;
			private List<R> results;

			public InclusiveMatcherThenImpl(T key, bool doFunc, List<R> results)
			{
				this.key = key;
				this.doFunc = doFunc;
				this.results = results;
			}

			public InclusiveMatcherCase<T, R> Then(Func<T, R> func)
			{
				if (doFunc)
					results.Add(func(key));

				return new InclusiveMatcherCaseImpl<T, R>(key, results);
			}
		}
		
		private struct InclusiveMatcherThenTempTypeImpl<T, U, R> : InclusiveMatcherThenTempType<T, U, R>
		{
			private T key;
			private bool doFunc;
			private List<R> results;

			public InclusiveMatcherThenTempTypeImpl(T key, bool doFunc, List<R> results)
			{
				this.key = key;
				this.doFunc = doFunc;
				this.results = results;
			}

			public InclusiveMatcherCase<T, R> Then(Func<U, R> func)
			{
				if (doFunc)
					results.Add(func((U) (Object) key));

				return new InclusiveMatcherCaseImpl<T, R>(key, results);
			}
		}

		public static TThens CaseNull<TThens, T>(this Cases<TThens, T> matcher)
		{
			return matcher.Case(x => x == null);
		}

		public static TThens CaseNotNull<TThens, T>(this Cases<TThens, T> matcher)
		{
			return matcher.Case(x => x != null);
		}

		public static TThens CaseSame<TThens, T>(this Cases<TThens, T> matcher, T obj)
		{
			return matcher.Case(x => Object.ReferenceEquals(x, obj));
		}

		public static TThens CaseNotSame<TThens, T>(this Cases<TThens, T> matcher, T obj)
		{
			return matcher.Case(x => ! Object.ReferenceEquals(x, obj));
		}

		public static TThens Case<TThens, T>(this Cases<TThens, T> matcher, T obj)
		{
			return matcher.Case(x => Object.Equals(x, obj));
		}

		public static TThens Case<TThens, T>(this Cases<TThens, T> matcher, bool cond)
		{
			return matcher.Case(x => cond);
		}

		public static TThens Case<TThens, T>(this Cases<TThens, T> matcher, Func<bool> cond)
		{
			return matcher.Case(x => cond());
		}

        public static TThens Case<TThens, A>(this Cases<TThens, Tuple<A>> matcher, Func<A, bool> cond)
        {
            return matcher.Case(x => cond(x.Item1));
        }

        public static TThens Case<TThens, A, B>(this Cases<TThens, Tuple<A, B>> matcher, Func<A, B, bool> cond)
        {
            return matcher.Case(x => cond(x.Item1, x.Item2));
        }

        public static TThens Case<TThens, A, B, C>(this Cases<TThens, Tuple<A, B, C>> matcher, Func<A, B, C, bool> cond)
        {
            return matcher.Case(x => cond(x.Item1, x.Item2, x.Item3));
        }

        public static TThens Case<TThens, A, B, C, D>(this Cases<TThens, Tuple<A, B, C, D>> matcher, Func<A, B, C, D, bool> cond)
        {
            return matcher.Case(x => cond(x.Item1, x.Item2, x.Item3, x.Item4));
        }

        public static TThens Case<TThens, A, B, C, D, E>(this Cases<TThens, Tuple<A, B, C, D, E>> matcher, Func<A, B, C, D, E, bool> cond)
        {
            return matcher.Case(x => cond(x.Item1, x.Item2, x.Item3, x.Item4, x.Item5));
        }

		public static TThens Case<TThens>(this Cases<TThens, String> matcher, Regex regex)
		{
			return matcher.Case(regex.IsMatch);
		}

		public static TThens CaseRegex<TThens>(this Cases<TThens, String> matcher, String pattern)
		{
			return matcher.Case(x => Regex.IsMatch(x, pattern));
		}

		public static TThens CaseType<TThens, T>(this Cases<TThens, T> matcher, Type type)
		{
			return matcher.Case(x => x == null ? false : type.IsAssignableFrom(x.GetType()));
		}

		public static R Else<T, R>(this MatcherCase<T, R> matcher, R result)
		{
			return matcher.Else(x => result);
		}

		public static R Else<T, R>(this MatcherCase<T, R> matcher, Func<R> result)
		{
			return matcher.Else(x => result());
		}

        public static R Else<A, R>(this MatcherCase<Tuple<A>, R> matcher, Func<A, R> result)
        {
            return matcher.Else(x => result(x.Item1));
        }

        public static R Else<A, B, R>(this MatcherCase<Tuple<A, B>, R> matcher, Func<A, B, R> result)
        {
            return matcher.Else(x => result(x.Item1, x.Item2));
        }

        public static R Else<A, B, C, R>(this MatcherCase<Tuple<A, B, C>, R> matcher, Func<A, B, C, R> result)
        {
            return matcher.Else(x => result(x.Item1, x.Item2, x.Item3));
        }

        public static R Else<A, B, C, D, R>(this MatcherCase<Tuple<A, B, C, D>, R> matcher, Func<A, B, C, D, R> result)
        {
            return matcher.Else(x => result(x.Item1, x.Item2, x.Item3, x.Item4));
        }

        public static R Else<A, B, C, D, E, R>(this MatcherCase<Tuple<A, B, C, D, E>, R> matcher, Func<A, B, C, D, E, R> result)
        {
            return matcher.Else(x => result(x.Item1, x.Item2, x.Item3, x.Item4, x.Item5));
        }

		public static R ElseDefault<T, R>(this MatcherCase<T, R> matcher)
		{
			return matcher.Else(x => default(R));
		}

		public static R ElseThrow<T, R>(this MatcherCase<T, R> matcher, Exception exc)
		{
			return matcher.Else(x => { throw exc; });
		}

		public static TCases Throw<TCases, T>(this ThensAction<TCases, T> matcher, Exception exc)
		{
			return matcher.Then(x => { throw exc; });
		}

		public static TCases Throw<TCases, T, R>(this ThensFunc<TCases, T, R> matcher, Exception exc)
		{
			return matcher.Then(x => { throw exc; });
		}

		public static TCases Then<TCases, T, R>(this ThensFunc<TCases, T, R> matcher, R result)
		{
			return matcher.Then(x => result);
		}

		public static TCases Then<TCases, T, R>(this ThensFunc<TCases, T, R> matcher, Func<R> result)
		{
			return matcher.Then(x => result());
		}

        public static TCases Then<TCases, A, R>(this ThensFunc<TCases, Tuple<A>, R> matcher, Func<A, R> result)
        {
            return matcher.Then(x => result(x.Item1));
        }

        public static TCases Then<TCases, A, B, R>(this ThensFunc<TCases, Tuple<A, B>, R> matcher, Func<A, B, R> result)
        {
            return matcher.Then(x => result(x.Item1, x.Item2));
        }

        public static TCases Then<TCases, A, B, C, R>(this ThensFunc<TCases, Tuple<A, B, C>, R> matcher, Func<A, B, C, R> result)
        {
            return matcher.Then(x => result(x.Item1, x.Item2, x.Item3));
        }

        public static TCases Then<TCases, A, B, C, D, R>(this ThensFunc<TCases, Tuple<A, B, C, D>, R> matcher, Func<A, B, C, D, R> result)
        {
            return matcher.Then(x => result(x.Item1, x.Item2, x.Item3, x.Item4));
        }

        public static TCases Then<TCases, A, B, C, D, E, R>(this ThensFunc<TCases, Tuple<A, B, C, D, E>, R> matcher, Func<A, B, C, D, E, R> result)
        {
            return matcher.Then(x => result(x.Item1, x.Item2, x.Item3, x.Item4, x.Item5));
        }
	}

	public interface Cases<TThens, T>
	{
		TThens Case(Func<T, bool> predicate);
	}

	public interface ThensAction<TCases, T>
	{
		TCases Then(Action<T> func);
	}

	public interface ThensActionTempType<TCases, T, U> : ThensAction<TCases, U> {}

	public interface ThensFunc<TCases, T, R>
	{
		TCases Then(Func<T, R> func);
	}

	public interface ThensFuncTempType<TCases, T, U, R> : ThensFunc<TCases, U, R> {}

	public interface Matcher0<T> : Cases<MatcherThen<T>, T>
	{
		MatcherThenTempType<T, U> Case<U>();
		InclusiveMatcher0<T> Inclusive();
		MatcherCase<T, R> Return<R>();
	}

	public interface InclusiveMatcher0<T> : Cases<InclusiveMatcherThen<T>, T>
	{
		InclusiveMatcherThenTempType<T, U> Case<U>();
		InclusiveMatcherCase<T, R> Return<R>();
	}

	public interface MatcherCase<T> : Cases<MatcherThen<T>, T>
	{
		MatcherThenTempType<T, U> Case<U>();
		void Else(Action<T> func);
	}

	public interface MatcherThen<T> : ThensAction<MatcherCase<T>, T> {}

	public interface MatcherThenTempType<T, U> : ThensActionTempType<MatcherCase<T>, T, U> {}

	public abstract class MatcherCase<T, R> : Cases<MatcherThen<T, R>, T>
	{
		internal MatcherCase() {}

		public abstract MatcherThen<T, R> Case(Func<T, bool> predicate);
		public abstract MatcherThenTempType<T, U, R> Case<U>();
		public abstract R Else(Func<T, R> func);
	}

	public interface MatcherThen<T, R> : ThensFunc<MatcherCase<T, R>, T, R> {}

	public interface MatcherThenTempType<T, U, R> : ThensFuncTempType<MatcherCase<T, R>, T, U, R> {}

	public interface InclusiveMatcherCase<T> : Cases<InclusiveMatcherThen<T>, T>
	{
		InclusiveMatcherThenTempType<T, U> Case<U>();
	}

	public interface InclusiveMatcherThen<T> : ThensAction<InclusiveMatcherCase<T>, T> {}

	public interface InclusiveMatcherThenTempType<T, U> : ThensActionTempType<InclusiveMatcherCase<T>, T, U> {}

	public abstract class InclusiveMatcherCase<T, R> : Cases<InclusiveMatcherThen<T, R>, T>
	{
		internal InclusiveMatcherCase() {}

		public abstract InclusiveMatcherThen<T, R> Case(Func<T, bool> predicate);
		public abstract InclusiveMatcherThenTempType<T, U, R> Case<U>();
		public abstract List<R> Eval();

		public static implicit operator List<R>(InclusiveMatcherCase<T, R> matcher)
		{
			return matcher.Eval();
		}
	}

	public interface InclusiveMatcherThen<T, R> : ThensFunc<InclusiveMatcherCase<T, R>, T, R> {}

	public interface InclusiveMatcherThenTempType<T, U, R> : ThensFuncTempType<InclusiveMatcherCase<T, R>, T, U, R> {}
}
